/*
 * Copyright (C) 2009 Leandro Aparecido <lehphyro@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.metalframework.jsf.el;

import java.util.*;

import javax.el.*;

import org.metalframework.jsf.action.*;
import org.metalframework.jsf.scope.*;
import org.springframework.web.jsf.el.*;

public class MetalFacesELResolver extends SpringBeanFacesELResolver {
	
	private static final String ACTION_RESULT_PROPERTY = "actionResult";
	private static final String ACTION_PARAM_PROPERTY = "actionParam";
	
	@Override
	public Object getValue(ELContext elContext, Object base, Object property) throws ELException {
		if (base == null) {
			String name = property.toString();
			
			if (isActionProperty(name)) {
				elContext.setPropertyResolved(true);
				return getActionProperty(name);
			}

			FacesScope facesScope = getFacesScopeContaining(name);
			if (facesScope != null) {
				elContext.setPropertyResolved(true);
				return facesScope.get(name);
			}

		}
		return super.getValue(elContext, base, property);
	}

	@Override
	public Class<?> getType(ELContext elContext, Object base, Object property) throws ELException {
		if (base == null) {
			String name = property.toString();
			
			if (isActionProperty(name)) {
				elContext.setPropertyResolved(true);
				return getActionPropertyType(name);
			}

			FacesScope facesScope = getFacesScopeContaining(name);
			if (facesScope != null) {
				Object value = facesScope.get(name);
				if (value == null) {
					throw new IllegalArgumentException("Cannot determine type of attribute [" + name + "] in the scope [" + getFacesScopeContaining(name) + "] because ir is null");
				}
				elContext.setPropertyResolved(true);
				return value.getClass();
			}
		}
		return super.getType(elContext, base, property);
	}
	
	@Override
	public void setValue(ELContext elContext, Object base, Object property, Object value) throws ELException {
		if (base == null) {
			String name = property.toString();
			
			if (isActionProperty(name)) {
				throw new PropertyNotWritableException("Variable [" + name + "] refers to a action invocation property which is not writable by definition");
			}
			
			FacesScope facesScope = getFacesScopeContaining(name);
			if (facesScope != null) {
				elContext.setPropertyResolved(true);
				facesScope.set(name, value);
				return;
			}
		}
		super.setValue(elContext, base, property, value);
	}
	
	@Override
	public boolean isReadOnly(ELContext elContext, Object base, Object property) throws ELException {
		if (base == null) {
			String name = property.toString();
			
			if (isActionProperty(name)) {
				return true;
			}
			
			FacesScope facesScope = getFacesScopeContaining(name);
			if (facesScope != null) {
				return false;
			}
		}
		return super.isReadOnly(elContext, base, property);
	}
	
	protected FacesScope getFacesScopeContaining(String name) {
		for (FacesScope scope : FacesScope.getAllScopes()) {
			if (scope.contains(name)) {
				return scope;
			}
		}
		return null;
	}
	
	protected boolean isActionProperty(String name) {
		return name.equals(ACTION_RESULT_PROPERTY) || name.equals(ACTION_PARAM_PROPERTY);
	}
	
	protected Object getActionProperty(String name) {
		ActionInvocation invocation = ActionInvocationAccessor.getRequiredActionInvocation();
		
		if (ACTION_RESULT_PROPERTY.equals(name)) {
			return invocation.getReturnValue();
		} else if (ACTION_PARAM_PROPERTY.equals(name)) {
			return invocation.getArgumentsWithNames();
		}
		
		throw new IllegalArgumentException("Unknown action context variable: " + name);
	}
	
	protected Class<?> getActionPropertyType(String name) {
		ActionInvocation invocation = ActionInvocationAccessor.getRequiredActionInvocation();
		
		if (ACTION_RESULT_PROPERTY.equals(name)) {
			return invocation.getMethod().getReturnType();
		} else if (ACTION_PARAM_PROPERTY.equals(name)) {
			return Map.class;
		}
		
		throw new IllegalArgumentException("Unknown action context variable: " + name);
	}
}
